﻿#region using

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Microsoft.Office.Interop.Access;
using MsBuildAccess;
using MsBuildAccess.Helper;
using AccessObject=MsBuildAccess.Common.AccessObject;

#endregion

namespace MsBuildAccess.Common
{
    /// <summary>
    /// MsAccess Application class
    /// </summary>
    public class MsAccessFile : IDisposable
    {
        #region fields

        /// <summary>
        /// Accesstype accdb,mdb,adp
        /// </summary>
        private readonly AccessType accessType;

        /// <summary>
        /// access filename
        /// </summary>
        private readonly string filename;

        /// <summary>
        /// Logger class
        /// </summary>
        private readonly Logger logger;

        /// <summary>
        /// access application class
        /// </summary>
        private ApplicationClass accessApplication = new ApplicationClass();

        /// <summary>
        /// object is disposed
        /// </summary>
        private bool disposed;

        #endregion
        
        /// <summary>
        /// Initializes a new instance of the MsAccessFile class.
        /// </summary>
        /// <param name="fileName">Source controlled microsoft access file</param>
        /// <param name="accessType">MsAccess file type</param>
        /// <param name="compileModuleName">Module name for compiling database</param>
        /// <param name="logger">msbuild logger</param>
        public MsAccessFile(string fileName, AccessType accessType, string compileModuleName, Logger logger)
        {
            this.accessType = accessType;
            this.filename = fileName;
            this.CompileModuleName = compileModuleName;
            this.logger = logger;
        }

        #region properties

        /// <summary>
        /// Database is open
        /// </summary>
        public bool IsOpen { get; private set; }

        /// <summary>
        /// Has error in last operation
        /// </summary>
        public bool IsError { get; private set; }

        /// <summary>
        /// Open this module and compile (will compile all module)
        /// </summary>
        public string CompileModuleName { get; private set; }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// dispes MsAccesFile instance
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion

        /// <summary>
        /// Open access file
        /// </summary>
        /// <returns>true when success</returns>
        public bool Open()
        {
            bool ret = true;
            try
            {
                if (this.accessType == AccessType.mdb || this.accessType == AccessType.accdb)
                {
                    this.accessApplication.OpenCurrentDatabase(this.filename, false, string.Empty);
                }
                else
                {                    
                    this.accessApplication.OpenAccessProject(this.filename, false);
                }

                this.accessApplication.Visible = false;
                this.IsOpen = true;
            }
            catch (Exception ex)
            {
                this.IsOpen = false;
                ret = false;
                this.SetErrorMessage(ex);
            }

            return ret;
        }

        /// <summary>
        /// cloase accessobject
        /// </summary>
        /// <returns>true when success</returns>
        public bool Close()
        {
            bool ret = true;
            try
            {
                if (this.IsOpen && !this.IsError)
                {
                    // todo accessproject have to close???????
                    this.accessApplication.CloseCurrentDatabase();
                }
            }
            catch (Exception ex)
            {
                ret = false;
                this.SetErrorMessage(ex);
            }

            return ret;
        }

        /// <summary>
        /// Compile and Save file
        /// </summary>
        /// <param name="customReferences">references for vba codes</param>
        /// <returns>true when success</returns>
        public bool CompileAndSave(List<AccessReference> customReferences)
        {
            bool ret = true;
            try
            {
                if (this.IsOpen && !this.IsError)
                {
                    this.AddReferences(customReferences);
                    if (!string.IsNullOrEmpty(this.CompileModuleName))
                    {
                        this.accessApplication.DoCmd.OpenModule(this.CompileModuleName, string.Empty);
                        this.accessApplication.DoCmd.RunCommand(AcCommand.acCmdCompileAndSaveAllModules);
                        if (!this.accessApplication.IsCompiled)
                        {
                            this.SetErrorMessage(
                                new Exception(
                                    "Could not compile database, please open the file and try compile manually to find out the error!"));
                        }

                        this.accessApplication.DoCmd.Close(AcObjectType.acForm, this.CompileModuleName, AcCloseSave.acSaveYes);
                    }
                }
            }
            catch (Exception ex)
            {
                ret = false;
                this.SetErrorMessage(ex);
            }

            return ret;
        }

        /// <summary>
        /// quit application
        /// </summary>
        /// <returns>true when success</returns>
        public bool Quit()
        {
            bool ret = true;
            try
            {
                if (this.IsOpen)
                {
                    this.accessApplication.DoCmd.Quit(AcQuitOption.acQuitSaveAll);
                }
                else
                {
                    this.accessApplication.DoCmd.Quit(AcQuitOption.acQuitSaveNone);
                }
            }
            catch (Exception ex)
            {
                ret = false;
                this.SetErrorMessage(ex);
            }

            return ret;
        }

        /// <summary>
        /// Import one file
        /// </summary>
        /// <param name="accessObject">initialized access object</param>
        /// <returns>true when success</returns>
        public bool ImportOneFile(AccessObject accessObject)
        {
            bool ret = true;
            try
            {                
                this.accessApplication.LoadFromText(accessObject.ObjectType, accessObject.ObjectName, accessObject.FileName);
            }
            catch (Exception ex)
            {
                ret = false;
                this.SetErrorMessage(ex);
            }

            return ret;
        }

        private void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    // Dispose managed resources.
                    Marshal.ReleaseComObject(this.accessApplication);
                    this.accessApplication = null;
                }

                this.disposed = true;
            }
        }

        /// <summary>
        /// Set an error message
        /// </summary>
        /// <param name="ex">exception instance</param>
        private void SetErrorMessage(Exception ex)
        {
            this.IsError = true;
            this.logger.LogErr(ex.ToString());
        }

        /// <summary>
        /// AddReferences for vba code
        /// </summary>    
        /// <param name="customReferences">references for vba codes</param>        
        private void AddReferences(ICollection<AccessReference> customReferences)
        {
            var builtIn = new List<string>();
            if (customReferences != null && customReferences.Count > 0)
            {
                ////firs we remove all not bultin references
                foreach (Reference reference in this.accessApplication.References)
                {
                    if (!reference.BuiltIn)
                    {
                        this.accessApplication.References.Remove(reference);
                    }
                    else
                    {
                        builtIn.Add(reference.Name);
                    }

                    Marshal.ReleaseComObject(reference);
                }
                ////add custom references
                foreach (AccessReference item in customReferences)
                {
                    AccessReference reference = item;
                    if (!builtIn.Exists(s => reference.Name.ToLower() == s.ToLower()))
                    {
                        this.accessApplication.References.AddFromGuid(item.Guid, item.Major, item.Minor);
                    }
                }
            }

            ////validate refences
            foreach (Reference reference in accessApplication.References)
            {
                ////if broken try add without Major and minor
                if (reference.IsBroken)
                {
                    string guid = reference.Guid;
                    this.accessApplication.References.Remove(reference);
                    this.accessApplication.References.AddFromGuid(guid, 0, 0);
                }

                Marshal.ReleaseComObject(reference);
            }
        }

        /// <summary>
        /// compact and repair database
        /// </summary>
        /// <param name="sourceFile">source file</param>
        /// <param name="destinationFile">destination file</param>
        /// <returns></returns>
        public bool CompactRepair(string sourceFile,string destinationFile)
        {
            try
            {                             
                //this.accessApplication.ConvertAccessProject(AcFileFormat.acFileFormatAccess2007);                
                return this.accessApplication.CompactRepair(sourceFile, destinationFile, false);
            }
            catch (Exception ex)
            {
                SetErrorMessage(ex);                
                return false;
            }
            
        }

        /// <summary>
        /// Make Mde (with undocumented syscmd action flag)
        /// </summary>
        /// <param name="sourceFile">source file</param>
        /// <param name="destinationFile">destination file</param>
        /// <returns></returns>
        public bool MakeMde(string sourceFile,string destinationFile)
        {
           try
           {
               logger.LogMsg(string.Format("Makemde {0} {1}", sourceFile, destinationFile));
               this.accessApplication.SysCmd((AcSysCmdAction)603, sourceFile, destinationFile);
               return true;
           }
            catch(Exception ex)
            {
                SetErrorMessage(ex);
                return false;
            }
        }

        /// <summary>
        /// Convert msaccess to another format type 
        /// </summary>
        /// <param name="sourceFile">source file</param>
        /// <param name="destinationFile">destination file</param>
        /// <param name="acFileFormat">destination type</param>
        /// <returns></returns>
        public bool ConverAccess(string sourceFile, string destinationFile, AcFileFormat acFileFormat)
        {
            
            this.accessApplication.ConvertAccessProject( sourceFile, destinationFile,acFileFormat);
            return true;   
        }
        
    }
}